package com.auicyh.tylb.comm;

import android.os.Handler;
import android.os.Message;

import com.auicyh.tylb.constant.ErrorCode;
import com.auicyh.tylb.utils.DataConversion;
import com.auicyh.tylb.utils.LogUtils;
import com.auicyh.tylb.utils.StringUtils;
import com.auicyh.tylb.utils.TylbException;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.ref.WeakReference;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;

/**
 * 功能：串口读写类
 * @author Ocean 2018/4/23 10:05
 * 邮箱：   1126067113@qq.com
 * 修改人：
 * 公司: 北京天元陆兵汽车科技有限公司
 */
public class SerialHelper {
    /** 串口对象 */
    private SerialPort mSerialPort;
    /** 输出流，用来发送字节数据 */
    private OutputStream mOutputStream;
    /** 输入流，用来接受字节数据 */
    private InputStream mInputStream;
    /** 串口号 */
    private String mPort = "/dev/ttysWK1";
    /** 波特率 */
    private int mBaudRate = 115200;
    /** 多指令读延时时间 */
    private int mDelay = 100;
    /** 是否已经打开串口(默认) */
    private volatile boolean mbIsOpen = false;


    /** 读取串口数据的指令 */
    private ReadThread mReadThread;
    /** 单回调监听 */
    private SerialPortListener mSerialPortListener;
    /** handle */
    private final MyHandler mHandler = new MyHandler(this);

    /**
     * 构造函数
     * @param port     串口名
     * @param baudRate 波特率
     */
    public SerialHelper(String port, int baudRate) {
        mPort = port;
        mBaudRate = baudRate;
    }

    /**
     * 构造函数，默认波特率
     * @param port 串口名
     */
    public SerialHelper(String port) {
        this(port, 115200);
    }


    /**
     * 打开串口
     * @throws TylbException 无效参数异常，端口号或波特率无效时抛出
     */
    public void open() throws TylbException {
        mSerialPort = new SerialPort(new File(mPort), mBaudRate, 0);
        mOutputStream = mSerialPort.getOutputStream();
        mInputStream = mSerialPort.getInputStream();
        mReadThread = new ReadThread();
        mReadThread.start();
        // 更新串口状态
        mbIsOpen = true;
    }

    /**
     * 发送串口数据
     * @param data 数据
     * @throws TylbException 指令发送异常
     */
    public synchronized void send(byte[] data) throws TylbException {
        try {
            // 这里清空数组具有一定的风险,对于MoreSendThread,读和写不在一个线程
            // 再发指令清空数组可能会造成,读取未NULL
            mOutputStream.write(data);
        } catch (IOException e) {
            throw new TylbException("指令发送失败，请再次尝试连接电路板。", ErrorCode.ERROR_PCB_SEND_205);
        }
    }


    /**
     * 获取输出流
     * @return mOutputStream
     */
    public OutputStream getOutputStream() {
        return mOutputStream;
    }


    public InputStream getInputStream() {
        return mInputStream;
    }


    /**
     * 发送16进制字符串命令
     * @param hex 十六进制字符串
     * @throws TylbException IO异常
     */
    public void senHex(String hex) throws TylbException {
        byte[] bOutArray = DataConversion.hexToByteArr(hex);
        send(bOutArray);
    }

    /**
     * 发送字符串数据
     * @param text 字符串
     * @throws TylbException IO异常
     */
    public void sendText(String text) throws TylbException {
        byte[] bOutArray = text.getBytes();
        send(bOutArray);
    }

    /**
     * 获取波特率
     * @return 波特率
     */
    public int getBaudRate() {
        return mBaudRate;
    }

    /**
     * 设置波特率
     * @param baudRate 波特率
     * @return true 设置成功，false 串口已开启，修改无效
     */
    public boolean setBaudRate(int baudRate) {
        if (mbIsOpen) {
            return false;
        } else {
            mBaudRate = baudRate;
            return true;
        }
    }

    /**
     * 设置波特率
     * @param baudRate 波特率
     * @return true 设置成功，false 串口已开启，修改无效
     */
    public boolean setBaudRate(String baudRate) {
        int iBaud = Integer.parseInt(baudRate);
        return setBaudRate(iBaud);
    }

    /**
     * 获取串口名
     * @return 串口名
     */
    public String getPort() {
        return mPort;
    }

    /**
     * 设置串口名
     * @param port 串口名
     * @return true 设置成功，false 串口已开启，修改无效
     */
    public boolean setPort(String port) {
        if (mbIsOpen) {
            return false;
        } else {
            if (!StringUtils.isStrNull(port)) {
                mPort = port;
            }
            return true;
        }
    }


    /**
     * 串口是否已开启
     * @return true 串口已开启, false 串口已关闭
     */
    public boolean isOpen() {
        return mbIsOpen;
    }

    /**
     * 获取延时时间
     * @return mDelay
     */
    public int getDelay() {
        return mDelay;
    }

    /**
     * 设置读取串口数据延时时间
     * @param delay delay
     */
    public void setDelay(int delay) {
        mDelay = delay;
    }

    public void setSerialPortListener(SerialPortListener serialPortListener) {
        mSerialPortListener = serialPortListener;
    }


    /**
     * 关闭串口
     */
    public void onCancel() {
        mbIsOpen = false;
        mHandler.removeCallbacksAndMessages(null);
        if (mReadThread != null) {
            mReadThread.interrupt();
        }
        if (mSerialPort != null) {
            mSerialPort.close();
        }
        if (mInputStream != null) {
            try {
                mInputStream.close();
                mInputStream = null;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * 读取数据，适用于循环读取一个指令的同时在发送其它指令，但需要开启一个发送指令的线程
     * 比如，循环读取温度的同时，操作某一个电磁阀
     * 注意：对于多指令,有6秒的超时时间,即两个指令的间隔不大于6秒
     * 数据读取,只负责读
     */
    private class ReadThread extends Thread {
        @Override
        public void run() {
            super.run();
            // 记录为null次数
            int cont = 0;
            try {
                while (mbIsOpen) {
                    cont++;
                    Thread.sleep(mDelay);
                    byte[] buffer = new byte[1024];
                    if (mInputStream != null) {
                        int available = mInputStream.available();
                        if (available == 0) {
                            Message message = new Message();
                            message.obj = cont;
                            message.what = ErrorCode.PCB_READING_217;
                            mHandler.sendMessage(message);
                        } else {
                            cont = 0;
                            int size = mInputStream.read(buffer);
                            if (size > 0) {
                                // 这里读取成功后,继续循环,等待下一个指令
                                Message message = new Message();
                                message.obj = buffer;
                                message.what = ErrorCode.PCB_READ_BYTE_216;
                                mHandler.sendMessage(message);
                            }
                        }
                    }
                }
            } catch (InterruptedException e) {
                // 串口中断异常
                mbIsOpen = false;
                if (null != mSerialPortListener) {
                    mSerialPortListener.onFinish(ErrorCode.ERROR_THREAD_SLEEP_202);
                }
            } catch (IOException e) {
                mbIsOpen = false;
                if (null != mSerialPortListener) {
                    mSerialPortListener.onFinish(ErrorCode.ERROR_PCB_SEND_IO_206);
                }
            } finally {
                mbIsOpen = false;
                try {
                    if (null != mInputStream) {
                        // 4.4.2上报空指针
                        mInputStream.close();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }


    /**
     * 接受串口数据的Handle
     */
    private static class MyHandler extends Handler {
        private final WeakReference<SerialHelper> mSerialHelper;

        private MyHandler(SerialHelper helper) {
            mSerialHelper = new WeakReference<>(helper);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            SerialHelper helper = mSerialHelper.get();
            synchronized (SerialHelper.class) {
                if (!helper.mbIsOpen) {
                    return;
                }
                switch (msg.what) {
                    case ErrorCode.PCB_READ_TIMEOUT_218:
                        // 读取超时
                        if (null != helper.mSerialPortListener) {
                            helper.mSerialPortListener.onFinish(ErrorCode.PCB_READ_TIMEOUT_218);
                        }
                        break;
                    case ErrorCode.PCB_READING_217:
                        // 读取中
                        int count = (int) msg.obj;
                        if (null != helper.mSerialPortListener) {
                            helper.mSerialPortListener.onWait(count);
                        }
                        break;
                    case ErrorCode.PCB_READ_BYTE_216:
                        // 读取成功
                        byte[] bytes = (byte[]) msg.obj;
                        byte cmd = (byte) (bytes[1] - 0x40);
                        // 返回值长度
                        int length = DataConversion.byteToInt(bytes[0]) + 2;
                        // 校验字
                        byte check = 0x40;
                        if (null != helper.mSerialPortListener) {
                            // 转hex
                            String hex = DataConversion.byteArrToHex(bytes, 0, length);
                            // 累加和校验
                            boolean checkData = DataConversion.checkData(bytes, cmd, length, check);
                            helper.mSerialPortListener.onReceive(cmd, bytes, hex, checkData);
                        }
                        break;
                    default:

                        break;
                }
            }
        }
    }


    /**
     * 串口监听
     */
    public interface SerialPortListener {
        /**
         * 等待可以操作UI
         * @param count count最大为30
         */
        void onWait(int count);

        /**
         * 串口读到的数据,可以操作UI
         * @param cmd   发送时的指令
         * @param bytes 返回的byte数组
         * @param hex   返回的hex
         * @param check 校验结果
         */
        void onReceive(byte cmd, byte[] bytes, String hex, boolean check);

        /**
         * 读取结束,请勿在这个方法中操作UI。
         * @param statCode 状态码
         */
        void onFinish(int statCode);

    }


    /**
     * 计算电压
     * 返回值格式： 09 46 x1 x2 y1 y2 z1 z2 w1 w2 cs 共11位
     * 公式: 电压计算方式：(w1*256+w2)/1000
     * 返回值说明： x1 x2 新油重量 y1 y2 旧油重量 z1 z2 温度 w1 w2 供电电压(高位在前，低位在后)
     * @param bytes bytes数组
     * @param w1    电压的高位 8
     * @param w2    电压的低位 9
     * @return 电压值
     */
    public double getVoltage(byte[] bytes, int w1, int w2) {
        return (bytes[w1] * 256 + bytes[w2]) / 1000.00;
    }


    /**
     * 获取压强
     * 公式 Vt = Vc(0.8P + 0.1)  计算的单位 mPa
     * 1mPa = 10bar
     * 1巴(bar)=1.0197162公斤力/平方厘米(kgf/cm²)
     * 1兆帕(MPa) = 10.1971621公斤力/平方厘米(kgf/cm²)
     * @param bytes bytes数组
     * @param w1    电压的高位
     * @param w2    电压的低位
     * @return 压强 单位是 公斤力/平方厘米(kgf/cm²)
     */
    public double getPressure(byte[] bytes, int w1, int w2) {
        double voltage = getVoltage(bytes, w1, w2);
        double p;
        // 最小电压
        double minVoltage = 0.5;
        if (voltage < minVoltage) {
            p = 0;
        } else {
            // 1兆帕(MPa) = 10.1971621公斤力/平方厘米(kgf/cm²)
            p = (voltage / 5 - 0.1) / 0.8 * 10.1971621;
            BigDecimal decimal = new BigDecimal(p);
            p = decimal.setScale(3, BigDecimal.ROUND_DOWN).doubleValue();
        }
        return p;
    }

    /**
     * 读取温度
     * 返回值格式： 09 46 x1 x2 y1 y2 z1 z2 w1 w2 cs 共11位
     * 返回值说明： x1 x2 新油重量 y1 y2 旧油重量 z1 z2 温度 w1 w2 供电电压(高位在前，低位在后)
     * 计算温度：如果z1的高5位为0，则温度值为 (z1*256+z2)/16，
     * 若 temp == 0xFFFF，表示温度出错
     * 若 响应字节z1的高5位为1，则温度值为负，此时，温度值为将z1与z2按位取反加1，然后结果除以16.
     * @param bytes bytes数组
     * @param z1    电压的高位 6
     * @param z2    电压的低位 7
     * @return 温度
     */
    public double getTemp(byte[] bytes, int z1, int z2) {
        double temp;
        // 计算值 相当于 x1*256 + x2 如果返回值是 0xffff 表示温度出错
        //           (int) bytes[z1] * 256.0 + (bytes[z2] & 0xff)
        double d = ((bytes[z1] & 0xFF) << 8) + (bytes[z2] & 0xFF);
        double v = 0xffff;
        if (d == v) {
            // 温度出错
            temp = 0.0;
        } else {
            // 温度解析
            int f = 0xF8;
            if ((bytes[z1] & f) <= 0) {
                // 计算温度
                temp = d / 16.0;
            } else {
                // 将z1与z2按位取反加1(取补码)，然后结果除以16.0
                // ocean 取反 连接  加1
                temp = ((~bytes[z1]) << 8) + (~bytes[z2]) + 1;
                temp = -(temp / 16.0);
            }
        }
        return temp;
    }

    /**
     * 电子秤校准,获取电子称的基准值和斜率
     * 计算斜率  k = u*1000/(f - i); k 斜率，u用户输入的值，f加油之后的值，i加油之前的值
     * L = (r - v)*k/1000.0 L 升数 r实时值 v 数据库读出的v k 斜率
     * 通过用户输入的值、加油前后的值，计算出新的斜率，通过新的斜率和实时值 算出升数，然后和用户输入的值进行误差校验，在
     * 允许的范围呢，校准成功，否则抛出异常。
     * @param userInputValue 用户输入的值
     * @param baseline       基准值,加油之前的值
     * @param original       加油之后的值
     * @throws TylbException 异常
     */
    public Map<String, Double> getScaleBaseline(double userInputValue, double baseline, double original) throws TylbException {
        // 差
        double poor = original - baseline;
        // 最小差值
        double minPoor = 50;
        if (poor < minPoor || baseline == 0) {
            // 加油之后的值 <= 加油之前的值，抛出305异常
            throw new TylbException("电子秤校准失败，加油前后差值过小，请检查加油时机，重新校准。", ErrorCode.ERROR_SAYS_305);
        }
        // 计算斜率  k = u*1000/(f - i); k 斜率，u用户输入的值，f加油之后的值，i加油之前的值   即一升油增加量的比值
        // 一个单位的增加量
        double aug = original - baseline;
        double slope = userInputValue * 1000 / aug;
        if (Double.isInfinite(slope)) {
            throw new TylbException("斜率K无穷大异常", ErrorCode.ERROR_SAYS_301);
        }
        BigDecimal decimal = new BigDecimal(slope);
        slope = decimal.setScale(2, BigDecimal.ROUND_DOWN).doubleValue();
        if (slope == 0) {
            throw new TylbException("电子秤校准失败，斜率为0。", ErrorCode.ERROR_SAYS_307);
        }
        Map<String, Double> map = new HashMap<>(2);
        map.put("slope", slope);
        map.put("baseline", baseline);
        return map;
    }

    /**
     * 获取电子秤的值
     * 计算油量(注销的地方未负数判断，电子称示数可以为负数，这样当电子称出问题时会更直观)
     * 计算升数 [(x1 & 0xff << 8 + x2 & 0xff) - v] * k/1000
     * @param retArray retArray
     * @param h        高位  2或者4
     * @param b        底位  3或者5
     * @param baseline 基准值
     * @param k        斜率
     */
    public Map<String, Double> getScaleValue(byte[] retArray, int h, int b, double baseline, double k) {
        if (b > retArray.length) {
            return null;
        }
        Map<String, Double> map = new HashMap<>(2);
        // 原值(换算成升数之前的值)
        double originalValue = ((retArray[h] & 0xff) << 8) + (retArray[b] & 0xff);
        originalValue = originalValue - originalValue % 10;
        // 升数 liter
        double liter = (originalValue - baseline) * k / 1000.0;
        BigDecimal decimal = new BigDecimal(liter);
        BigDecimal setScale = decimal.setScale(2, BigDecimal.ROUND_DOWN);
        try {
            map.put("original", originalValue);
            map.put("liter", setScale.doubleValue());
        } catch (Exception e) {
            return null;
        }
        return map;
    }

    public SerialPort getSerialPort() {
        return mSerialPort;
    }
}
